package com.sql.mysql.sharding.mapper;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.SQLException;

import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;

import com.sql.mysql.sharding.annotation.ShardingKeyObject;
import com.sql.mysql.sharding.annotation.ShardingKeyType;
import com.sql.mysql.sharding.reflection.ShardingReflectionHelper;
import com.sql.mysql.sharding.session.ShardingSqlSessionFactory;
import com.sql.mysql.sharding.threadlocal.ShardingThreadLocal;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ShardingMapperUtils {
	static class MapperInvoker<T> implements InvocationHandler, Serializable {
		/**
		 * 
		 */
		private static final long serialVersionUID = -5556427256761687656L;
		private T mapper;
		private SqlSession sqlSession;
		private boolean closed = false;

		public MapperInvoker(T m, SqlSession s) {
			mapper = m;
			sqlSession = s;
		}

		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			// 如果是执行普通的方法,比如toString(),比如触发日志打印就会失败
			{
				if (Object.class.equals(method.getDeclaringClass())) {
					return method.invoke(this, args);
				}
			}
			if (closed) {
				throw new SQLException("this mapper is closed...");
			}
			// 执行
			// 之前的别影响我
			ShardingThreadLocal.reset(true, false);
			log.debug("清除了所有线程变量" + System.currentTimeMillis());
			try {
				// 安装ShardingKeyObject
				ShardingKeyObject shardingKey = ShardingReflectionHelper.getCopiedShardingKeyObject(method);
				if (null == shardingKey) {
					throw new Exception("fail to find sharding key for method " + method);
				}
				// [预处理]如果是参数下标指定法则,则现在就需要立刻赋值
				if (ShardingKeyType.PARAMETER_INDEX == shardingKey.getAnnotationType()) {
					shardingKey.setValue(args[shardingKey.getParameterIndex()]);
				}
				ShardingThreadLocal.ShardingKeyObjectThreadLocal.set(shardingKey);
				// 继续处理
				Object result = method.invoke(mapper, args);
				return result;
			} catch (Exception e) {
				log.error("{}", e);
				throw e;
			} finally {
				// 不管执行过程中发生了什么,都要释放连接到连接池
				sqlSession.close();// 考虑到读写分离,这里必须关闭--->释放连接到连接池
				sqlSession = null;// help GC
				closed = true;
				// 也不给别人留障碍
				ShardingThreadLocal.reset(true, false);
			}
		}
	}

	// 分库分表插件只支持SIMPLE类型,其它不支持
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static <T> T getMapper(Class<T> clazz) {
		SqlSession sqlSession = ShardingSqlSessionFactory.getSqlSessionFactory().openSession(ExecutorType.SIMPLE, true);
		T mapper = sqlSession.getMapper(clazz);
		T proxy = (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz },
				new MapperInvoker(mapper, sqlSession));
		return proxy;
	}

}
